package me.prettyprint.cassandra.service.template;

import java.util.HashMap;
import java.util.Map;

import me.prettyprint.cassandra.model.HSlicePredicate;
import me.prettyprint.cassandra.model.thrift.ThriftColumnFactory;
import me.prettyprint.cassandra.service.ExceptionsTranslator;
import me.prettyprint.cassandra.service.ExceptionsTranslatorImpl;
import me.prettyprint.hector.api.ColumnFactory;
import me.prettyprint.hector.api.ConsistencyLevelPolicy;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.Serializer;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.MutationResult;
import me.prettyprint.hector.api.mutation.Mutator;

import org.apache.cassandra.thrift.ColumnParent;

public class AbstractColumnFamilyTemplate<K, N> {
  // Used for queries where we just ask for all columns
  public static final int ALL_COLUMNS_COUNT = Integer.MAX_VALUE;
  public static final Object ALL_COLUMNS_START = null;
  public static final Object ALL_COLUMNS_END = null;

  protected Keyspace keyspace;
  protected String columnFamily;
  protected Serializer<K> keySerializer;
  protected Map<N, Serializer<?>> columnValueSerializers;
  protected ColumnParent columnParent;
  protected HSlicePredicate<N> activeSlicePredicate;
  protected ColumnFactory columnFactory;
  protected ConsistencyLevelPolicy consistencyLevelPolicy;
  
  /** The serializer for a standard column name or a super-column name */
  protected Serializer<N> topSerializer;

  /**
   * By default, execute updates automatically at common-sense points such as
   * after queuing the updates of all an object's properties. Or, in the case of
   * multiple objects, at the end of the list. No Mutator executes() will be
   * called if this is set to true. This allows an arbitrary number of updates
   * to be performed and executed manually.
   */
  protected boolean batched;

  /**
   * An optional clock value to pass to deletes. If null, the default value
   * generated by Hector is used
   */
  protected Long clock;
  
  protected ExceptionsTranslator exceptionsTranslator;
  
  public AbstractColumnFamilyTemplate(Keyspace keyspace, String columnFamily,
      Serializer<K> keySerializer, Serializer<N> topSerializer) {
    // ugly, but safe
    this.keyspace = keyspace;
    this.columnFamily = columnFamily;
    this.keySerializer = keySerializer;
    this.topSerializer = topSerializer;
    columnValueSerializers = new HashMap<N, Serializer<?>>();
    this.columnParent = new ColumnParent(columnFamily);
    this.activeSlicePredicate = new HSlicePredicate<N>(topSerializer);
    exceptionsTranslator = new ExceptionsTranslatorImpl();
    this.columnFactory = new ThriftColumnFactory();
    setCount(100);
  }


  /**
   * Add a column to the static set of columns which will be used in constructing 
   * the single-argument form of slicing operations
   * @param columnName
   * @param valueSerializer
   */
  public AbstractColumnFamilyTemplate<K,N> addColumn(N columnName, Serializer<?> valueSerializer) {
    columnValueSerializers.put(columnName, valueSerializer);
    activeSlicePredicate.addColumnName(columnName);
    return this;
  }
  
  /**
   * Get the value serializer for a given column. Returns null if none found
   * @param columnName
   * @return
   */
  public Serializer<?> getValueSerializer(N columnName) {
    return columnValueSerializers.get(columnName);
  }
   
  
  public boolean isBatched() {
    return batched;
  }

  public AbstractColumnFamilyTemplate<K, N> setBatched(boolean batched) {
    this.batched = batched;
    return this;
  }

  public String getColumnFamily() {
    return columnFamily;
  }

  public Serializer<K> getKeySerializer() {
    return keySerializer;
  }

  public Serializer<N> getTopSerializer() {
    return topSerializer;
  }

  public MutationResult executeBatch(Mutator<K> mutator) {    
    MutationResult result = mutator.execute();
    mutator.discardPendingMutations();
    return result;
  }

  public Mutator<K> createMutator() {
    return HFactory.createMutator(keyspace, keySerializer);
  }

  /**
   * Wrapped call to keyspace.createClock
   * To Specify a clock for a group of operations, use {@link AbstractTemplateUpdater} instead
   * @return
   */
  public long getClock() {
    return keyspace.createClock();
  }

  /**
   * @deprecated (and not thread-safe). Set clocks on the {@link AbstractTemplateUpdater}
   * implementation as needed. 
   * @param clock
   */
  public void setClock(Long clock) {
    this.clock = clock;
  }

  /**
   * @deprecated does the same thing as getClock() will be removed in a future release
   * @return
   */
  public long getEffectiveClock() {
    return keyspace.createClock();
  }  

  public void setExceptionsTranslator(ExceptionsTranslator exceptionsTranslator) {
    this.exceptionsTranslator = exceptionsTranslator;
  }    

  public void setColumnFactory(ColumnFactory columnFactory) {
    this.columnFactory = columnFactory;
  }

  protected MutationResult executeIfNotBatched(Mutator<K> mutator) {    
    return !isBatched() ? executeBatch(mutator) : null;
  }
  
  protected MutationResult executeIfNotBatched(AbstractTemplateUpdater<K, N> updater) {    
    return !isBatched() ? executeBatch(updater.getCurrentMutator()) : null;
  }
  
  

  /**
   * Immediately delete this row in a single mutation operation
   * @param key
   */
  public void deleteRow(K key) {    
    createMutator().addDeletion(key, columnFamily, null, topSerializer).execute();
  }

  /**
   * Stage this deletion into the provided mutator calling {@linkplain #executeIfNotBatched(Mutator)}
   * @param mutator
   * @param key
   */
  public void deleteRow(Mutator<K> mutator, K key) {    
    mutator.addDeletion(key, columnFamily, null, topSerializer);
    executeIfNotBatched(mutator);
  }
   
  /**
   * Immediately delete this column as a single mutation operation
   * @param key
   * @param columnName
   */
  public void deleteColumn(K key, N columnName) {
    createMutator().addDeletion(key, columnFamily, columnName, topSerializer).execute();
  }
  
  /**
   * Stage this column deletion into the pending mutator. Calls {@linkplain #executeIfNotBatched(Mutator)}
   * @param mutator
   * @param key
   * @param columnName
   */
  public void deleteColumn(Mutator<K> mutator, K key, N columnName) {
    mutator.addDeletion(key, columnFamily, columnName, topSerializer);
    executeIfNotBatched(mutator);
  }

  /**
   * The number of columns to return when not doing a name-based template
   * @param count
   */
  public void setCount(int count) {
    activeSlicePredicate.setCount(count);
  }
}
